﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Warensoft.Unity.Communication.Client.Contracts;

namespace Warensoft.Unity.Communication.Client
{
    /// <summary>
    /// 
    /// </summary>
    public class SocketClient:IDisposable
    {
        private Socket clientSocket;
        private SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
        private SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
        private SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();
        byte[] receiveBuffer = new byte[1024];

        private ManualResetEvent waiteForConnectionSignal = new ManualResetEvent(false);
        private PackageManagerBase packageManager;
        /// <summary>
        /// 当连接状态发生改变时触发
        /// occurs when connection state changed
        /// </summary>
        public event EventHandler ConnectionStateChanged;
        /// <summary>
        /// 当收到新的数据包时触发
        /// occurs when a new data package received
        /// </summary>
        public event EventHandler<NewSocketMessageEventArgs> NewMessage;
        /// <summary>
        /// 当Socket连接发生错误时触发
        /// occurs when the socket connection raised an error
        /// </summary>
        /// <remarks>
        /// 该事件可能会在任务网络操作后被触发,如接收以及发送
        /// this event will be raised after any network operation,such as receiving,sending
        /// </remarks>
        public event EventHandler<SocketErrorEventArgs> SocketErrorOccured;
        private Action connectedCallback;

        public bool Connected
        {
            get {
                if (this.clientSocket ==null )
                {
                    return false;
                }
                return this.clientSocket.Connected;
            }
            
        }
        
        private Queue messageQueue=new Queue ();
        /// <summary>
        /// 获取消息包的队列
        /// get the queued message packages
        /// </summary>
        public Queue Message
        {
            get { return messageQueue; }
           
        }
        /// <summary>
        /// 构造方法
        /// constructor
        /// </summary>
        /// <remarks>
        /// 默认构造方法将使用WarensoftPackageManager类做为默认的组包管理器
        /// 如果开发者希望自己控制数据组包拆包的过程,请使用重载过的构造方法,并提供一个自定义的组包类
        /// 该类要继承自PackageManagerBase类
        /// the default constructor will use "WarensoftPackageManager" class as the default package manager
        /// if the developer wants to manipulate the process of packaging and unpackaging, then use the overloaded constructor
        /// and provide and custom message packager class which inherit from PackageManagerBase
        /// </remarks>
        internal SocketClient()
        {
            this.packageManager = new WarensoftPackageManager();
            this.Init();
        }
        /// <summary>
        /// 构造方法
        /// constructor
        /// </summary>
        /// <param name="packageManagerType">
        /// 手动指定拆包组包类的类型
        /// specify the type of data packager class
        /// </param>
        internal SocketClient(Type packageManagerType)
        {
            this.packageManager = (PackageManagerBase)Activator.CreateInstance(packageManagerType);
            this.Init();
        }
        private void Init()
        {
            this.clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.sendArgs.Completed += sendArg_Completed;
            CommunicationManager.SceneUpdate += CommunicationManager_SceneUpdate;
            

        }
        /// <summary>
        /// 连接远程服务器
        /// <para>
        /// connect the remote serverr
        /// </para>
        /// </summary>
        /// <param name="ip">
        /// 服务器IP
        /// IP address of the server
        /// </param>
        /// <param name="port">
        /// 端口号
        /// port
        /// </param>
        public void Connect(IPAddress ip, int port)
        {
            connectArgs.Completed += connectArgs_Completed;
            connectArgs.RemoteEndPoint = new IPEndPoint(ip, port);
            
            var result= this.clientSocket.ConnectAsync(connectArgs);
        }
        /// <summary>
        /// 断开连接
        /// <para>
        /// disconnect
        /// </para>
        /// </summary>
        public void Disconnect()
        {
            this.clientSocket.Close();
        }

        void connectArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                if (this.ConnectionStateChanged != null)
                {
                    this.ConnectionStateChanged(this, new EventArgs());

                }
                this.StartListen();
            }
            else
            {
                this.RaiseSocketError(e.SocketError);
                this.Disconnect();
            }
        }
        void CommunicationManager_SceneUpdate(object sender, EventArgs e)
        {
            while (this.messageQueue.Count != 0 && this.NewMessage != null)
            {
                var top = this.messageQueue.Dequeue()  ;
                
                this.NewMessage(this, new NewSocketMessageEventArgs() { Message = top });
            }
        }

        
        private void StartListen()
        {
            this.receiveArgs .Completed +=(sender,e)=>
            {
                if (e.SocketError == SocketError.Success )
                {
                    var newMessages=this.packageManager.AddToQueue(e.Buffer,0,e.BytesTransferred );
                    foreach (var item in newMessages )
                    {
                        this.messageQueue.Enqueue(item);
                    }
                    if (this.clientSocket .Connected)
                    {
                        this.clientSocket.ReceiveAsync(this.receiveArgs);
                    }
                }
                else
                {
                    this.RaiseSocketError(e.SocketError);
                    this.Disconnect();
                }
            };
            this.receiveArgs.SetBuffer(receiveBuffer,0,this.receiveBuffer .Length );
            this.clientSocket.ReceiveAsync(this.receiveArgs );
        }
        /// <summary>
        /// 向远程服务器发送消息
        /// send message to the remote server
        /// </summary>
        /// <remarks>
        /// 发送的消息要存储在一个IMessage接口的派生类中
        /// the message needs to be stored in a class which inherits from IMessage
        /// </remarks>
        /// <param name="message">消息存储体</param>
        public void SendMessage(IMessage message)
        {
            this.SendMessage(message.ToArray ());
        }
        /// <summary>
        /// 向连接服务器发送消息
        /// send message to the remote server
        /// </summary>
        /// <param name="buffer">
        /// 消息的字节流
        /// message bytes
        /// </param>
        public void SendMessage(byte[] buffer)
        {
            this.sendArgs.SetBuffer(buffer,0,buffer.Length );

            if (this.clientSocket .Connected )
            {
                this.clientSocket.SendAsync(this.sendArgs); 
            }
        }
        void sendArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success )
            {
                this.RaiseSocketError(e.SocketError );
                this.Disconnect();

            }
        }
        private void RaiseSocketError(SocketError error)
        {
            if (this.SocketErrorOccured !=null )
            {
                this.SocketErrorOccured(this, new SocketErrorEventArgs() { SocketError = error });
            }
        }
        /// <summary>
        /// 释放连接占用的相关资源
        /// dispose the resources
        /// </summary>
        public void Dispose()
        {
            this.Disconnect();
            this.connectArgs.Dispose();
            this.sendArgs.Dispose();
            this.receiveArgs.Dispose();
            this.receiveBuffer = null;
            this.messageQueue.Clear();
            this.messageQueue = null;
            GC.Collect();
        }
    }
}
